package com.atom.test.opengl

import android.graphics.*
import android.opengl.*
import android.os.Bundle
import android.util.Log
import android.view.Surface
import android.view.SurfaceHolder
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.atom.module.lib.opengles.draw.Gl2dMesh
import com.atom.module.lib.opengles.egl.EGLHelper
import com.atom.module.lib.opengles.egl.EGLWrapper
import com.atom.module.lib.opengles.ext.makeIdentity
import com.atom.module.lib.opengles.ext.scaleX
import com.atom.module.lib.opengles.ext.scaleY
import com.atom.module.lib.opengles.shader.GlScene
import com.atom.module.lib.opengles.shader.GlTextureProgram
import com.atom.module.lib.opengles.texture.GlTexture
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util
import javax.microedition.khronos.egl.EGL10
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.egl.EGLContext
import javax.microedition.khronos.egl.EGLDisplay
import javax.microedition.khronos.opengles.GL10

class VideoActivity : AppCompatActivity(), GLSurfaceView.Renderer {

    private lateinit var surfaceView: GLSurfaceView

    private var surfaceTexture: SurfaceTexture? = null
    private var surface: Surface? = null

    private var textureProgram: GlTextureProgram? = null

    private val scene = GlScene()
    private val drawable = Gl2dMesh().apply {
        this.setPoints(
            listOf(
                PointF(0F, 0F),//0
                PointF(0F, 1F),//1
                PointF(1F, 0F),//2
                PointF(0F, -1F),//3
                PointF(-1F, 0F),//4
                PointF(0.9F, 0.7F),//5
                PointF(0.7F, 0.9F),//6
                PointF(-0.9F, -0.7F),//7
                PointF(-0.7F, -0.9F),//8
                PointF(0.9F, -0.7F),//9
                PointF(0.7F, -0.9F),//10
                PointF(-0.9F, 0.7F),//11
                PointF(-0.7F, 0.9F)//12
            )
        )
    }

    private lateinit var player: SimpleExoPlayer

    private var videoWidth = -1
    private var videoHeight = -1
    private var surfaceWidth = -1
    private var surfaceHeight = -1

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_video)
        intent.data ?: let {
            finish()
            return
        }
        // exo player
        player = SimpleExoPlayer.Builder(this).build()
        val dataSourceFactory = DefaultDataSourceFactory(
            this,
            Util.getUserAgent(this, "Egloo")
        )
        val videoSource = ProgressiveMediaSource.Factory(dataSourceFactory)
            .createMediaSource(MediaItem.fromUri(intent.data!!))
        player.setMediaSource(videoSource)
        player.prepare()
        player.playWhenReady = true
        // opengl
        surfaceView = findViewById(R.id.glView)
        surfaceView.setZOrderOnTop(true)
        surfaceView.holder.setFormat(PixelFormat.RGBA_8888)
        surfaceView.setEGLContextFactory(Factory(2))
        surfaceView.setEGLConfigChooser(Chooser(2))
        surfaceView.setRenderer(this)
        surfaceView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
        surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceCreated(holder: SurfaceHolder) {

            }
            override fun surfaceChanged(
                holder: SurfaceHolder,
                format: Int,
                width: Int,
                height: Int
            ) {
            }
            override fun surfaceDestroyed(holder: SurfaceHolder) {
                onSurfaceDestroyed()
            }
        })
        // Address the aspect ratio
        player.addListener(object : Player.Listener{
            override fun onVideoSizeChanged(
                width: Int,
                height: Int,
                unappliedRotationDegrees: Int,
                pixelWidthHeightRatio: Float
            ) {
                super.onVideoSizeChanged(
                    width,
                    height,
                    unappliedRotationDegrees,
                    pixelWidthHeightRatio
                )
                videoWidth = width
                videoHeight = height
                onVideoOrSurfaceSizeChanged()
            }
        })
    }

    private fun onSurfaceDestroyed() {
        player.setVideoSurface(null)
        player.stop()
        player.release()

        textureProgram?.release()
        textureProgram = null

        surfaceTexture?.release()
        surfaceTexture = null

    }

    override fun onDestroy() {
        onSurfaceDestroyed()
        super.onDestroy()
    }

    override fun onDrawFrame(gl: GL10?) {
        val texture = surfaceTexture ?: return
        val program = textureProgram ?: return
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
        texture.updateTexImage()
        texture.getTransformMatrix(program.textureTransform)
        scene.draw(program, drawable)
    }

    private fun onVideoOrSurfaceSizeChanged() {
        if (videoWidth == -1 || videoHeight == -1) return
        if (surfaceWidth == -1 || surfaceHeight == -1) return
        val videoRatio = videoWidth.toFloat() / videoHeight
        val surfaceRatio = surfaceWidth.toFloat() / surfaceHeight
        val viewport: RectF
        if (videoRatio > surfaceRatio) {
            // Video is wider. We should collapse height.
            val surfaceRealHeight = surfaceWidth / videoRatio
            val surfaceRealHeightEgloo = surfaceRealHeight / surfaceHeight * 2
            viewport = RectF(
                -1F, surfaceRealHeightEgloo / 2F,
                1F, -surfaceRealHeightEgloo / 2F
            )
        } else if (videoRatio < surfaceRatio) {
            // Video is taller. We should collapse width
            val surfaceRealWidth = surfaceHeight * videoRatio
            val surfaceRealWidthEgloo = surfaceRealWidth / surfaceWidth * 2
            viewport = RectF(
                -surfaceRealWidthEgloo / 2F, 1F,
                surfaceRealWidthEgloo / 2F, -1F
            )
        } else {
            viewport = RectF(-1F, 1F, 1F, -1F)
        }

        drawable.modelMatrix.makeIdentity()
        drawable.modelMatrix.scaleX(viewport.width() / 2F)
        drawable.modelMatrix.scaleY(-viewport.height() / 2F)
    }


    class Factory(private val version: Int) : GLSurfaceView.EGLContextFactory {
        override fun createContext(
            egl: EGL10?,
            display: EGLDisplay?,
            eglConfig: EGLConfig?
        ): EGLContext {
            val attributes = intArrayOf(EGL14.EGL_CONTEXT_CLIENT_VERSION, version, EGL14.EGL_NONE)
            return egl?.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attributes)
                ?: throw RuntimeException("createContext $version")
        }

        override fun destroyContext(egl: EGL10, display: EGLDisplay?, context: EGLContext?) {
            if (!egl.eglDestroyContext(display, context)) {
                throw RuntimeException("eglDestroyContex" + egl.eglGetError())
            }
        }
    }

    class Chooser(private val version: Int) : GLSurfaceView.EGLConfigChooser {

        companion object {
            private const val EGL_RECORDABLE_ANDROID = 0x3142 // Android-specific extension.
        }

        // https://github.com/MasayukiSuda/ExoPlayerFilter/blob/master/epf/src/main/java/com/daasuu/epf/chooser/EConfigChooser.java
        override fun chooseConfig(egl: EGL10, display: EGLDisplay): EGLConfig {
            val configSizeArray = IntArray(1)
            var configSpec = getConfigSpec(version, true)
            if (!egl.eglChooseConfig(display, configSpec, null, 0, configSizeArray)) {
                throw IllegalArgumentException("eglChooseConfig failed")
            }
            var configSize = configSizeArray[0]
            if (configSize <= 0) {
                configSpec = getConfigSpec(version, false)
                if (!egl.eglChooseConfig(display, configSpec, null, 0, configSizeArray)) {
                    throw IllegalArgumentException("eglChooseConfig failed")
                }
                configSize = configSizeArray[0]
            }
            if (configSize <= 0) throw IllegalArgumentException("No configs match configSpec")

            val configs = arrayOfNulls<EGLConfig>(configSize)
            if (!egl.eglChooseConfig(display, configSpec, configs, configSize, configSizeArray)) {
                throw IllegalArgumentException("eglChooseConfig#2 failed")
            }
            return chooseConfig(egl, display, configs.filterNotNull().toTypedArray())
                ?: throw IllegalArgumentException("No config chosen")
        }


        // https://github.com/MasayukiSuda/ExoPlayerFilter/blob/master/epf/src/main/java/com/daasuu/epf/chooser/EConfigChooser.java
        private fun chooseConfig(
            egl: EGL10,
            display: EGLDisplay,
            configs: Array<EGLConfig>
        ): EGLConfig? {
            for (config in configs) {
                val d = egl.findConfigAttrib(display, config, EGL10.EGL_DEPTH_SIZE, 0)
                val s = egl.findConfigAttrib(display, config, EGL10.EGL_STENCIL_SIZE, 0)
                if (d >= 0 && s >= 0) {
                    val r = egl.findConfigAttrib(display, config, EGL10.EGL_RED_SIZE, 0)
                    val g = egl.findConfigAttrib(display, config, EGL10.EGL_GREEN_SIZE, 0)
                    val b = egl.findConfigAttrib(display, config, EGL10.EGL_BLUE_SIZE, 0)
                    val a = egl.findConfigAttrib(display, config, EGL10.EGL_ALPHA_SIZE, 0)
                    if (r == 8 && g == 8 && b == 8 && a == 8) {
                        return config
                    }
                }
            }
            return null
        }

        private fun EGL10.findConfigAttrib(
            display: EGLDisplay,
            config: EGLConfig,
            attribute: Int,
            defaultValue: Int
        ): Int {
            val value = IntArray(1)
            return if (eglGetConfigAttrib(display, config, attribute, value)) {
                value[0]
            } else defaultValue
        }

        internal fun getConfigSpec(version: Int, recordable: Boolean): IntArray {
            val renderableType = if (version >= 3) {
                EGL14.EGL_OPENGL_ES2_BIT or EGLExt.EGL_OPENGL_ES3_BIT_KHR
            } else {
                EGL14.EGL_OPENGL_ES2_BIT
            }
            return intArrayOf(
                EGL14.EGL_RED_SIZE, 8,
                EGL14.EGL_GREEN_SIZE, 8,
                EGL14.EGL_BLUE_SIZE, 8,
                EGL14.EGL_ALPHA_SIZE, 8,
                // We can create both window surfaces and pbuffer surfaces.
                EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT or EGL14.EGL_PBUFFER_BIT,
                EGL14.EGL_RENDERABLE_TYPE, renderableType,
                if (recordable) EGL_RECORDABLE_ANDROID else EGL14.EGL_NONE,
                if (recordable) 1 else 0,
                EGL14.EGL_NONE
            )
        }
    }

    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
        textureProgram = GlTextureProgram( glTexture = GlTexture())

        surfaceTexture = SurfaceTexture(textureProgram!!.glTexture.id)
        surfaceTexture?.setOnFrameAvailableListener {
            surfaceView.requestRender()
        }

        surface = Surface(surfaceTexture!!)
        surfaceView.post {
            player.setVideoSurface(surface)
        }
    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
        GLES20.glViewport(0, 0, width, height)
        scene.setViewportSize(width, height)
        surfaceWidth = width
        surfaceHeight = height
        onVideoOrSurfaceSizeChanged()
    }
}